package com.leinbo.onedoc.controller

import com.kotoframework.core.condition.eq
import com.kotoframework.core.condition.or
import com.kotoframework.core.future.from
import com.kotoframework.function.create.Patch.execute
import com.kotoframework.function.create.create
import com.kotoframework.function.remove.remove
import com.kotoframework.function.select.select
import com.kotoframework.function.update.update
import com.kotoframework.interfaces.KPojo
import com.kotoframework.interfaces.Patch.execute
import com.kotoframework.utils.Common.currentDate
import com.leinbo.onedoc.kpojo.Config
import com.leinbo.onedoc.kpojo.Config.Companion.initWaterMark
import com.leinbo.onedoc.kpojo.Node
import com.leinbo.onedoc.kpojo.Response
import com.leinbo.onedoc.utils.CommonUtil.activeRequest
import com.leinbo.onedoc.utils.CommonUtil.addWaterMark
import com.leinbo.onedoc.utils.CommonUtil.getRequestAddress
import com.leinbo.onedoc.utils.CommonUtil.globalConfig
import com.leinbo.onedoc.utils.CommonUtil.transform2Htm
import com.leinbo.onedoc.utils.CommonUtil.transform2Pdf
import com.leinbo.onedoc.utils.DownloadUtil.download
import com.leinbo.onedoc.utils.ExcelUtil.namesOfSheets
import com.leinbo.onedoc.utils.ExcelUtil.numberOfSheets
import org.apache.commons.io.FilenameUtils
import org.springframework.util.DigestUtils
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import java.io.File
import java.net.URL
import java.net.URLDecoder

/**
 * Created by sundaiyue on 2022/11/23 14:10
 */
@RestController
@RequestMapping("/api")
class ResourceController {
    data class Resource(
        val fileName: String? = null,
        val fileType: String? = null,
        val fileSize: String? = null,
        val pdfUrl: String? = null,
        val coverUrl: String? = null,
        val html: List<String>? = null,
        val stylesheet: String? = null,
        val sheetNames: List<String>? = listOf(),
        val status: String? = null,
    )

    data class Online(
        val url: String?,
        val reload: Boolean? = false,
        val waterMark: Config.Companion.WatermarkConfig? = Config.Companion.WatermarkConfig(),
    ) : KPojo


    @PostMapping("/resource")
    fun online(
        @RequestBody online: Online
    ): Response {
        if (online.url == null) {
            return Response.error("请传入文件地址")
        } // 未传入文件地址

        if (globalConfig.downloadPath == null) {
            return Response.error("请配置文件下载路径")
        } // 未配置文件下载路径

        val decodedUrl = URLDecoder.decode(online.url, "UTF-8") // 解码
        val resourceURL = URL(decodedUrl) // 资源地址
        val extension = FilenameUtils.getExtension(resourceURL.path) // 文件后缀
        val fileName = FilenameUtils.getName(resourceURL.path) // 文件名
        val pathToFile = "${globalConfig.downloadPath}/$currentDate/${System.currentTimeMillis()}" // 文件路径

        if (!globalConfig.allowedExtensions.contains(extension)) {
            return Response.error("不支持的文件格式")
        } // 不支持的文件格式

        if (online.reload != true) { // 若强行刷新则不检查文件是否存在
            val saved = checkFileExist(
                url = decodedUrl, newNode = Node(
                    nodeName = fileName,
                    nodeUrl = decodedUrl,
                    nodeType = extension,
                    status = "读取文档中"
                )
            ) // 根据文件地址检查文件是否存在
            if (saved != null) {
                return Response.success(saved)
            }
        }

        val file = download( // 下载文件
            resourceURL, pathToFile, fileName
        )

        if (file == null) { // 下载失败
            remove(Node(nodeUrl = decodedUrl)).where().execute()
            return Response.error("文件下载失败, 请检查文件地址是否正确")
        }

        val md5 = file.inputStream().use { DigestUtils.md5DigestAsHex(it) } // 计算文件md5

        if (online.reload != true) { // 若强行刷新则不检查文件是否存在
            val saved = checkFileExist(
                decodedUrl, md5, file, Node(
                    nodeName = fileName,
                    nodeMd5 = md5,
                    nodeUrl = decodedUrl,
                    nodeType = extension,
                    status = "解析文档中"
                )
            ) // 根据文件md5检查文件是否存在
            if (saved != null) {
                return Response.success(saved)
            }
        }

        val (pdfPath, coverPath) = transform2Pdf(file) // 转换为pdf

        if (online.waterMark != null) { // 添加水印
            addWaterMark(pdfPath, initWaterMark(online.waterMark))
        }
        val htmls = transform2Htm(file) // 转换为html
        val sheetNames = file.namesOfSheets // 获取sheet名

        val node = Node(
            nodeName = fileName,
            nodeUrl = decodedUrl,
            nodeType = extension,
            nodeMd5 = md5,
            nodeSize = file.length() / 1024.0 / 1024.0,
            absolutePath = file.absolutePath,
            sheetNum = file.numberOfSheets,
            sheetNames = sheetNames.joinToString("//"),
            htmls = htmls,
            pdfPath = pdfPath,
            cover = coverPath,
            status = "已完成"
        )

        update(node).except(node::id).by(node::nodeMd5).execute()

        val html = if (sheetNames.size == 1) {
            listOf(File(htmls).readText())
        } else if (sheetNames.size > 1) {
            getMutiHtmls(htmls, sheetNames.size)
        } else {
            null
        }
        val stylesheet = if (sheetNames.size > 1) {
            File(htmls.replace(".html", "_files/stylesheet.css")).readText()
        } else {
            null
        }

        return Response.success(
            Resource(
                fileName = node.nodeName,
                fileType = extension,
                fileSize = String.format("%.2f", node.nodeSize),
                pdfUrl = pdfPath.pathToUrl(),
                coverUrl = coverPath.pathToUrl(),
                html = html,
                stylesheet = stylesheet,
                sheetNames = sheetNames,
                status = "已完成"
            )
        )
    }

    fun String.pathToUrl(): String {
        val relativePath = this.replace("\\","/").substringAfter(globalConfig.downloadPath!!)
        return "${getRequestAddress(activeRequest)}/resource/$relativePath"
    }

    fun getMutiHtmls(path: String, num: Int): List<String> {
        return (1..num).map {
            File(String.format("%s/sheet%03d.htm", path.replace(".html", "_files"), it)).readText()
        }
    }

    fun checkFileExist(
        url: String? = null,
        md5: String? = null,
        temp: File? = null,
        newNode: Node
    ): Resource? {
        val node = from<Node> {
            it.select().where(it::nodeMd5.eq(md5) or it::nodeUrl.eq(url))
        }.queryForObjectOrNull()

        if (node != null && node.status == "已完成") { // 找到相同结点
            temp?.parentFile?.deleteRecursively() // 删除临时文件
            temp?.delete()

            val sheetNames = node.sheetNames!!.split("//").filter { it.isNotBlank() } // 分割sheet名

            val html = if (sheetNames.size == 1) { // 单页
                listOf(File(node.htmls!!).readText())
            } else if (sheetNames.size > 1) { // 多页
                getMutiHtmls(node.htmls!!, sheetNames.size)
            } else { // 未知
                null
            }

            val stylesheet = if (sheetNames.size > 1) {
                File(node.htmls!!.replace(".html", "_files/stylesheet.css")).readText()
            } else {
                null
            }

            return Resource(
                fileName = node.nodeName,
                fileType = node.nodeType,
                fileSize = String.format("%.2f", node.nodeSize),
                pdfUrl = node.pdfPath!!.pathToUrl(),
                coverUrl = node.cover!!.pathToUrl(),
                html = html,
                stylesheet = stylesheet,
                sheetNames = sheetNames,
                status = node.status
            )
        } else {
            if (node == null) { // 未找到相同文件，创建新记录
                create(newNode).execute()
            } else {
                if(md5 != null){ //若md5不为空，根据nodeUrl更新记录的md5
                    update(newNode).except(newNode::id).by(newNode::nodeUrl).execute()
                } else { // 若md5为空，说明是重复的且未完成的任务，直接返回
                    return Resource(
                        fileName = node.nodeName,
                        fileType = node.nodeType,
                        status = node.status
                    )
                }
            }
            return null
        }
    }
}
